#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <regex.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <fcntl.h>
#include <stdint.h>
#include <elf.h>
#include <errno.h>

// #define DEBUG
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

struct memory_section {
	unsigned long host_vaddr;
	unsigned long targ_vaddr;
	unsigned long size;
	struct memory_section *next;
};

struct memory_section section;

struct logbuf_list {
	unsigned long prev;
	unsigned long next;
	unsigned long elem;
};

char *log_type[] = {
	[0] = "  MAIN  ",
	[1] = "  RADIO ",
	[2] = " EVENTS ",
	[3] = " SYSTEM ",
	[4] = "  CRASH ",
	[5] = "SECURITY",
	[6] = " KERNEL ",
};

/* From LogBufferElement.h */
/*
can check with gdb commands as follows:
(gdb) p *(class LogBufferElement*)0x0000007df8e73000
(gdb) x/16x 0x0000007df8e73000
0x7df8e73000:   0x00000064e94323d0      0x0000000000000006
0x7df8e73010:   0x0000000000000000      0x0000007df8e73040
0x7df8e73020:   0x0000000000000033      0x0000000000000001
0x7df8e73030:   0x251c890b0000008a      0x0000000000000000
0x7df8e73040:   0x2820302030630004      0x2972657070617773
0x7df8e73050:   0x676e69746f6f4220      0x6f2078756e694c20
0x7df8e73060:   0x636973796870206e      0x3020555043206c61
0x7df8e73070:   0x0000000000003078      0x0000000000000000
*/
struct LogBufferElement {
	uint64_t vtable;
	uint32_t mLogId;
	uint32_t mUid;
	uint32_t mPid;
	uint32_t mTid;
	uint64_t mMsg;
	uint16_t mMsgLen;
	uint16_t padding[3];
	uint64_t mSequence;
	uint32_t tv_sec;
	uint32_t tv_nsec;
};

int find_memory_section(struct memory_section *section, void *memmap)
{
	Elf64_Ehdr *ehdr;
	Elf64_Phdr *phdr;
	struct memory_section *node;
	struct memory_section *next;
	int i;

	ehdr = memmap;
	if (ehdr->e_type != ET_CORE)
		return -1;
	if (ehdr->e_machine != EM_AARCH64)
		return -1;

	phdr = (void *)((unsigned long)ehdr + ehdr->e_phoff);
	node = section;
	for (i = 0; i < (int)ehdr->e_phnum; i++, phdr++) {
		if (phdr->p_type != PT_LOAD)
			continue;
		node->targ_vaddr = phdr->p_vaddr;
		node->host_vaddr = phdr->p_offset + (unsigned long)memmap;
		node->size = phdr->p_memsz;

		next = malloc(sizeof(struct memory_section));
		if (!next) {
			printf("[-] out of memory!\n");
			exit(-1);
		}
		memset(next, 0, sizeof(struct memory_section));
		node->next = next;
		node = next;
	}

	return 0;
}

unsigned long address_target_to_host(struct memory_section *section,
				     unsigned long targ_vaddr)
{
	struct memory_section *next;

	for (next = section; next->next != NULL; next = next->next) {
		if (targ_vaddr >= next->targ_vaddr &&
		    targ_vaddr <= (next->targ_vaddr + next->size))
			return targ_vaddr - next->targ_vaddr + next->host_vaddr;
	}
	return -1;
}

#define t2h(addr) address_target_to_host(&section, addr)

unsigned long address_host_to_target(struct memory_section *section,
				     unsigned long host_vaddr)
{
	struct memory_section *next;

	for (next = section; next->next != NULL; next = next->next) {
		if (host_vaddr >= next->host_vaddr &&
		    host_vaddr <= (next->host_vaddr + next->size))
			return host_vaddr - next->host_vaddr + next->targ_vaddr;
	}
	return -1;
}

#define h2t(addr) address_host_to_target(&section, addr)

int format_logcat(int logcat, unsigned long logbuf)
{
	struct logbuf_list *head;
	struct logbuf_list *next;
	struct LogBufferElement *elem;
	time_t tm_val;
	struct tm tm;
	int count;
	int msglen;
	char *buf;
	char *pmsg;
	int i;

	buf = malloc(4096);
	if (!buf) {
		printf("[-] out of memory!\n");
		exit(-1);
	}

	head = (struct logbuf_list *)t2h(logbuf);
	next = (struct logbuf_list *)t2h(head->next);
	while (next != head) {
		next = (struct logbuf_list *)t2h(next->next);
		if ((unsigned long) next == -1)
			return -1;
		elem = (struct LogBufferElement *)t2h(next->elem);
		if ((unsigned long)elem == -1)
			continue;

		tm_val = elem->tv_sec;
		localtime_r(&tm_val, &tm);
		count =
		    snprintf(buf, 128, "[:%s:%04d-%02d-%02d-%02d:%02d:%02d]",
			     log_type[elem->mLogId], tm.tm_year + 1900,
			     tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min,
			     tm.tm_sec);
		msglen = elem->mMsgLen;
		pmsg = (void *)t2h(elem->mMsg);
#ifdef DEBUG
		printf("msg=%lx msglen=%d\n", elem->mMsg, (int)elem->mMsgLen);
#endif
		switch (elem->mLogId) {
		case 2:	//LOG_ID_EVENTS
			pmsg += 9;
			msglen -= 9;	/* skip tag */
			count += snprintf(buf + count, 16, "<x>\t");
			break;
		case 5:	//LOG_ID_SECURITY
			break;
		case 0:	//LOG_ID_MAIN
		case 1:	//LOG_ID_RADIO
		case 3:	//LOG_ID_SYSTEM
		case 4:	//LOG_ID_CRASH
		case 6:	//LOG_ID_KERNEL
			count += snprintf(buf + count, 16, "<%d>", (int)*pmsg);
			if (pmsg[1] == 0) {
				msglen -= 2;
				pmsg += 2;
			} else {
				msglen -= 1;
				pmsg += 1;
			}
		}

		if (msglen <= 0)
			continue;

		/* s/\00/\t/ */
		for (i = 0; i < msglen - 1; i++) {
			if (pmsg[i] == 0)
				pmsg[i] = '\t';
		}

		memcpy(buf + count, (void *)pmsg, msglen);
		count += msglen;
		buf[count - 1] = '\n';
		count = write(logcat, buf, count);
	}
	free(buf);

	return 0;
}

int find_log_buf(char *memmap, unsigned long size, unsigned long *logBuf)
{
	unsigned long msg;
	uint16_t msglen;
	int found = 0;
	int i = 0;
	int j;
	int k;
	unsigned long *pmsg;
	uint16_t *pmsglen;
	unsigned long *pelem;
	unsigned long elem;	// struct LogBufferElement*
	struct logbuf_list *list, *next;
	char pattern1[] = { '\x05', '\x00', 'c' };
	char pattern2[] = { ' ', 'H', 'a', 'r', 'd', 'w', 'a', 'r', 'e',
		' ', 'n', 'a', 'm', 'e', ':'
	};

	/* step 1: find panic message */
	for (i = 0; i < size; i++) {
		if (memcmp(&memmap[i], pattern1, 3)) {
			continue;
		}
		j = i + 3;
		while ((memmap[j] >= '0' && memmap[j] <= '9') ||
		       memmap[j] == ' ')
			j++;
		if (memmap[j] != '(')
			continue;
#define PROG_MAX_NAME	64
		k = 0;
		while (memmap[j++] != ')' && k < PROG_MAX_NAME) {
			k++;
		}
		if (k == PROG_MAX_NAME)
			continue;
		if (memcmp(&memmap[j], pattern2, sizeof(pattern2)))
			continue;
		if (found == 1) {
			printf("[-] msg pattern found twice! can't process!\n");
			exit(-2);
		}
		found = 1;
		msg = h2t((unsigned long)&memmap[i]);
		while (memmap[j++] != 0) ;
		msglen = j - i;
#ifdef DEBUG
		printf("msg=%lx msglen=%d\n", msg, (int)msglen);
		for (k = 0; k < msglen; k++)
			printf("%c", memmap[k + i]);
		printf("\n");
#endif
	}

	if (found == 1) {
		found = 0;
	} else {
		printf("[-] msg pattern not found!\n");
		exit(1);
	}

	/* step 2: find elem */
	pmsg = (void *)memmap;

	for (i = 0; i < size / 8; i++) {
		pmsglen = (void *)&pmsg[i + 1];
		if (pmsg[i] == msg && *pmsglen == msglen) {
			if (found == 1) {
				printf
				    ("[-] elem pattern found twice! can't process!\n");
				exit(-2);
			}
			found = 1;
			elem =
			    h2t((unsigned long)&pmsg[i] -
				offsetof(struct LogBufferElement, mMsg));

		}
	}

	if (found == 1) {
		found = 0;
	} else {
		printf("[-] elem pattern not found!\n");
		exit(1);
	}

	/* step 3: find elem list */
	pelem = (void *)memmap;

	for (i = 2; i < size / 8; i++) {
		if (pelem[i] == elem &&
		    t2h(pelem[i - 1]) != -1 && t2h(pelem[i - 2]) != -1) {
			if (found == 1) {
				printf
				    ("[-] elem list pattern found twice! can't process!\n");
				exit(-2);
			}
			found = 1;
			list =
			    (struct logbuf_list *)h2t((unsigned long)
						      &pelem[i - 2]);
		}
	}

	if (found == 1) {
		found = 0;
	} else {
		printf("[-] elem list pattern not found!\n");
		exit(-1);
	}

	/* step 4: find list head */
	list = (struct logbuf_list *)t2h((unsigned long)list);
	next = (struct logbuf_list *)t2h(list->next);

	while (next != list) {
		next = (struct logbuf_list *)t2h(next->next);
		if (t2h(next->elem) == -1) {	/* found */
			*logBuf = h2t((unsigned long)next);
#ifdef DEBUG
			printf("find logBuf=%lx\n", *logBuf);
#endif
			return 0;
		}

	}

	printf("[-] logBuf not found!\n");
	exit(-2);

	return -1;
}

void usage(char *name)
{
	printf("usage: %s -c <corefile> [-b <logBuf address>]\n", name);
	printf("     -c is mandatory, -b is optional. Program will guess\n");
	printf("     logBuf address without -b argument.");
	printf("     To capture a corefile of logd, please use crash tool\n");
	printf("     the following commands is used in crash:\n");
	printf("     ps -G | grep logd; extend gcore.so; gcore <pid>;\n");
	printf
	    ("     logBuf address can be achieve through `p logBuf' in gdb\n");
}

#define VERSION		"0.1"

int main(int argc, char *argv[])
{
	int corefile_fd;
	int logcat_fd;
	off_t off;
	void *memmap;
	int ret;
	unsigned long logBuf = 0;
	int ch;
	char core_filename[64] = { 0 };

	while ((ch = getopt(argc, argv, "c:b:")) != EOF) {
		switch (ch) {
		case 'c':
			strncpy(core_filename, optarg, 64);
			break;
		case 'b':
			logBuf = strtol(optarg, NULL, 16);
			break;
		default:
			usage(argv[0]);
			return -1;
		}
	}

	if (core_filename[0] == 0) {
		usage(argv[0]);
		return -1;
	}

	printf("------- %s . version %s -------\n", argv[0], VERSION);

	corefile_fd = open(core_filename, O_RDONLY);
	if (corefile_fd < 0) {
		printf("[-] can't open corefile %s, exiting..\n", argv[1]);
		return -1;
	}

	off = lseek(corefile_fd, 0, SEEK_END);
	if (off == -1) {
		printf("[-] lseek failed\n");
		return -1;
	}
	lseek(corefile_fd, 0, SEEK_SET);

	memmap =
	    mmap(NULL, off, PROT_READ | PROT_WRITE, MAP_PRIVATE, corefile_fd,
		 0);
	if (memmap == (void *)-1) {
		printf("[-] mmap faield\n");
		return -1;
	}

	ret = find_memory_section(&section, memmap);
	if (ret < 0) {
		printf("[-] bad format of corefile!\n");
		return -1;
	}

	if (logBuf == 0)
		find_log_buf(memmap, off, &logBuf);
	logcat_fd = open("logcat.txt", O_TRUNC | O_RDWR | O_CREAT, 0666);
	if (logcat_fd < 0) {
		printf("[-] can't create file.\n");
		return -1;
	}
	format_logcat(logcat_fd, logBuf);

	close(corefile_fd);
	close(logcat_fd);

	printf("[*] Done. generate log file: logcat.txt\n");

	return 0;
}
